const fs = require('fs');
const path = require('path');
const babelParser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');

/**
 * 处理import中的alias，返回一个绝对路径
 * @param importPath
 * @param alias
 * @param entryPath
 * @returns {string}
 */
function importPathAlias(importPath, alias, entryPath) {
  const aliasArray = Object.entries(alias);
  for (let i = 0; i < aliasArray.length; i++) {
    const [aliasKey, aliasValue] = aliasArray[i];
    if (importPath.startsWith(aliasKey)) {
      return path.join(aliasValue, (importPath.replace(aliasKey, '.')));
    }
  }
  return path.join(path.dirname(entryPath), importPath);
}

/**
 * 为js文件增加js后缀
 * @param _path
 * @returns {string|*}
 */
function pathPatch(_path) {
  if (_path.endsWith('.js')) {
    return _path;
  }
  return `${_path}.js`;
}

/**
 * 获取basePath路径内的js文件的模块名称，名称规则为模块相对于路由根目录的位置，.js和文件路径中的特殊字符被_替代。
 * @param targetPath 路由模块文件
 * @param basePath 路由入口的根路径
 * @returns {string|(string|string)}
 */
function getModuleNameFromAbsolute(targetPath, basePath) {
  const moduleName = targetPath === basePath ? 'entry' : path.relative(path.dirname(basePath), targetPath)
    .replace(/\.js|\/|\\/ig, '_');

  if (moduleName.endsWith('_')) {
    return moduleName;
  }

  return `${moduleName}_`;
}

/**
 * 同步读取文件内容
 * @param filePath 文件的绝对路径，会自动补全.js后缀
 * @returns {string} 文件的内容
 */
function getFileContentSync(filePath) {
  return fs.readFileSync(filePath)
    .toString();
}

class ClientRouterProvider {
  /**
   * 构造函数
   * @param entry entry是路由配置文件的绝对路径
   * @param options
   */
  constructor(entry, options = {}) {
    this.entryAbsolutePath = entry;
    this.alias = options.alias || {};
    this.AggregationCode = '';
    this.routerIndexDependModulw = {};
    this.router = [];
    this.analysisRouterInfo();
  }

  /**
   * 读取router文件（index和module），需要确保路由文件为es6的写法
   * @param _filePath 读取的文件的绝对路径
   */
  handleRouterFile(_filePath) {
    const _this = this;
    const filePath = pathPatch(_filePath);
    const sourceCode = getFileContentSync(filePath);
    const moduleName = getModuleNameFromAbsolute(filePath, this.entryAbsolutePath);

    const AST = babelParser.parse(sourceCode, {
      sourceType: 'unambiguous',
      plugins: [],
    });

    traverse(AST, {
      /**
       * 将js文件使用function包起来
       * @param __path
       * @constructor
       */
      Program: (__path) => {
        __path.node.body = [(t.functionDeclaration({
          type: 'Identifier',
          name: `${moduleName}func`,
        }, [], {
          type: 'BlockStatement',
          body: __path.node.body,
        }))];
      },
      /**
       * 处理ast中的node.type为 ImportDeclaration 的node
       * 将 import A from './a.js' 转为 const A = a_func()
       * @param __path AST中的path
       * @returns object{moduleName:fileAbsolutePath} 返回的 moduleName 与const后的函数名前缀相对应
       */
      ImportDeclaration(__path) {
        const { node } = __path;
        node.source.value = importPathAlias(node.source.value, _this.alias, filePath);
        const { value } = node.source;

        // 如果 import 的文件不在 index 的同级或者子级目录，将其去掉
        if (!(value.startsWith(path.dirname(filePath)))) {
          __path.remove(node);
          return;
        }

        const _moduleName = `${getModuleNameFromAbsolute(value, _this.entryAbsolutePath)}`;
        node.specifiers.forEach((specify) => {
          const functionName = `${_moduleName}func`;
          Object.assign(_this.routerIndexDependModulw, { [functionName]: value });
          __path.replaceWith(t.variableDeclaration('const', [
            {
              type: 'VariableDeclarator',
              id: {
                type: 'Identifier',
                name: specify.local.name,
              },
              init: {
                type: 'CallExpression',
                callee: {
                  type: 'Identifier',
                  name: functionName,
                },
                arguments: [],
              },
            },
          ]));
        });
      },
      /**
       * 处理ast中的 node.type 为 ExportDefaultDeclaration 的node，将export default转为 return
       * @param _path
       */
      ExportDefaultDeclaration(__path) {
        __path.replaceWith(t.returnStatement(__path.node.declaration));
      },
      /**
       * 处理ast中的 node.type 为 ObjectExpression的node ，只保留该对象的keepProperties属性
       * @param node
       * @constructor
       */
      ObjectExpression({ node }) {
        const keepProperties = ['path', 'children', 'name', 'hidden'];
        if (node.type !== 'ObjectExpression') {
          return;
        }
        node.properties = node.properties.filter(
          property => keepProperties.includes(property.key.name)
        );
      },
    });

    const output = generate(AST, {
      retainLines: true,
      retainFunctionParens: true,
    }, sourceCode);

    const { code } = output;
    this.AggregationCode = `${this.AggregationCode}\n/* ${moduleName || 'entry_func'} */\n${code}`;
  }

  analysisRouterInfo() {
    this.handleRouterFile(this.entryAbsolutePath);

    while (Object.entries(this.routerIndexDependModulw)[0]) {
      const depObj = Object.entries(this.routerIndexDependModulw)[0];
      this.handleRouterFile(depObj[1]);
      delete this.routerIndexDependModulw[depObj[0]];
    }
  }

  get() {
    try {
      this.router = this.router.length
          // eslint-disable-next-line no-new-func
          ? this.router : (new Function('', `${this.AggregationCode}return entry_func()`))();
      return this.router;
    }catch (e) {
      return []
    }
  }
}

module.exports = ClientRouterProvider;
